#include "DetectorGC.h"
#include <zxing/common/GreyscaleLuminanceSource.h>
#include <zxing/common/detector/WhiteRectangleDetector.h>
#include <zxing/common/HybridBinarizer.h>
#include <newcode/GreyscaleGridSampler.h>

#include <cmath>

#ifdef DEBUG
#include <iostream>
#endif

namespace newcode{
namespace detector{

using namespace std;
Detector::Detector(Ref<ByteMatrix> image):
    datamatrix::Detector(binarize(image))
{
    image_ = image;
    //Now datamatrix::Detector holds binary variant of image
}

Ref<GrayscaleDetectorResult> Detector::detect()
{    
    Ref<WhiteRectangleDetector> rectangleDetector_(new WhiteRectangleDetector(
        datamatrix::Detector::getImage()));

    // The code below was copied from zxing::datamatrix::Detector::Detector from
    // ZXing project. Many thanx to them for this.
    std::vector<Ref<ResultPoint> > ResultPoints = rectangleDetector_->detect();
    Ref<ResultPoint> pointA = ResultPoints[0];
    Ref<ResultPoint> pointB = ResultPoints[1];
    Ref<ResultPoint> pointC = ResultPoints[2];
    Ref<ResultPoint> pointD = ResultPoints[3];

    // Point A and D are across the diagonal from one another,
    // as are B and C. Figure out which are the solid black lines
    // by counting transitions
    std::vector<Ref<datamatrix::ResultPointsAndTransitions> > transitions(4);
    transitions[0].reset(transitionsBetween(pointA, pointB));
    transitions[1].reset(transitionsBetween(pointA, pointC));
    transitions[2].reset(transitionsBetween(pointB, pointD));
    transitions[3].reset(transitionsBetween(pointC, pointD));
    insertionSort(transitions);

    // Sort by number of transitions. First two will be the two solid sides;
    // last two will be the two alternating black/white sides
    Ref<datamatrix::ResultPointsAndTransitions> lSideOne(transitions[0]);
    Ref<datamatrix::ResultPointsAndTransitions> lSideTwo(transitions[1]);

    // Figure out which point is their intersection by tallying up the number of
    // times we see the  endpoints in the four endpoints. One will show up
    // twice.
    Ref<ResultPoint> maybeTopLeft;
    Ref<ResultPoint> bottomLeft;
    Ref<ResultPoint> maybeBottomRight;
    if (lSideOne->getFrom()->equals(lSideOne->getTo())) {
      bottomLeft = lSideOne->getFrom();
      maybeTopLeft = lSideTwo->getFrom();
      maybeBottomRight = lSideTwo->getTo();
    } else if (lSideOne->getFrom()->equals(lSideTwo->getFrom())) {
      bottomLeft = lSideOne->getFrom();
      maybeTopLeft = lSideOne->getTo();
      maybeBottomRight = lSideTwo->getTo();
    } else if (lSideOne->getFrom()->equals(lSideTwo->getTo())) {
      bottomLeft = lSideOne->getFrom();
      maybeTopLeft = lSideOne->getTo();
      maybeBottomRight = lSideTwo->getFrom();
    } else if (lSideOne->getTo()->equals(lSideTwo->getFrom())) {
      bottomLeft = lSideOne->getTo();
      maybeTopLeft = lSideOne->getFrom();
      maybeBottomRight = lSideTwo->getTo();
    } else if (lSideOne->getTo()->equals(lSideTwo->getTo())) {
      bottomLeft = lSideOne->getTo();
      maybeTopLeft = lSideOne->getFrom();
      maybeBottomRight = lSideTwo->getFrom();
    } else {
      bottomLeft = lSideTwo->getFrom();
      maybeTopLeft = lSideOne->getTo();
      maybeBottomRight = lSideOne->getFrom();
    }

    // Bottom left is correct but top left and bottom right might be switched
    std::vector<Ref<ResultPoint> > corners(3);
    corners[0].reset(maybeTopLeft);
    corners[1].reset(bottomLeft);
    corners[2].reset(maybeBottomRight);

    // Use the dot product trick to sort them out
    ResultPoint::orderBestPatterns(corners);

    // Now we know which is which:
    Ref<ResultPoint> bottomRight(corners[0]);
    bottomLeft = corners[1];
    Ref<ResultPoint> topLeft(corners[2]);

    // Which point didn't we find in relation to the "L" sides? that's the top
    // right corner
    Ref<ResultPoint> topRight;
    if (!(pointA->equals(bottomRight) || pointA->equals(bottomLeft) ||
          pointA->equals(topLeft))) {
      topRight = pointA;
    } else if (!(pointB->equals(bottomRight) || pointB->equals(bottomLeft)
        || pointB->equals(topLeft))) {
      topRight = pointB;
    } else if (!(pointC->equals(bottomRight) || pointC->equals(bottomLeft)
        || pointC->equals(topLeft))) {
      topRight = pointC;
    } else {
      topRight = pointD;
    }

    // Next determine the dimension by tracing along the top or right side and
    // counting black/white transitions. Since we start inside a black module,
    // we should see a number of transitions equal to 1 less than the code
    // dimension. Well, actually 2 less, because we are going to end on a black
    // module:

    // The top right point is actually the corner of a module, which is one of
    // the two black modules adjacent to the white module at the top right.
    // Tracing to that corner from either the top left or bottom right should
    // work here.

    int dimensionTop = transitionsBetween(topLeft, topRight)->getTransitions();
    int dimensionRight = transitionsBetween(bottomRight, topRight)->
            getTransitions();

    //dimensionTop++;
    if ((dimensionTop & 0x01) == 1) {
      // it can't be odd, so, round... up?
      dimensionTop++;
    }
    dimensionTop += 2;

    //dimensionRight++;
    if ((dimensionRight & 0x01) == 1) {
      // it can't be odd, so, round... up?
      dimensionRight++;
    }
    dimensionRight += 2;

    Ref<ByteMatrix> bytes;
    Ref<PerspectiveTransform> transform;
    Ref<ResultPoint> correctedTopRight;

    int dimension = min(dimensionRight, dimensionTop);

    // correct top right point to match the white module
    correctedTopRight = correctTopRight(bottomLeft, bottomRight, topLeft,
                                        topRight, dimension);
    if (correctedTopRight == NULL) {
        correctedTopRight = topRight;
    }

    // Redetermine the dimension using the corrected top right point
    int dimensionCorrected = max(transitionsBetween(
        topLeft, correctedTopRight)->getTransitions(),
        transitionsBetween(bottomRight, correctedTopRight)->getTransitions());
    dimensionCorrected++;
    if ((dimensionCorrected & 0x01) == 1) {
        dimensionCorrected++;
    }

    transform = createTransform(topLeft, correctedTopRight, bottomLeft,
        bottomRight, dimensionCorrected, dimensionCorrected);
    bytes = sampleGrid(image_, dimensionCorrected, transform);
#   ifdef DEBUG
    cout << bytes->operator string() << *bytes;
#   endif
    correctContrast(bytes);
#   ifdef DEBUG
    cout << bytes->operator string() << *bytes;
#   endif
    std::vector<Ref<ResultPoint> > points(4);
    points[0].reset(topLeft);
    points[1].reset(bottomLeft);
    points[2].reset(correctedTopRight);
    points[3].reset(bottomRight);

    Ref<GrayscaleDetectorResult> detectorResult(
        new GrayscaleDetectorResult(bytes, points, transform));
    return detectorResult;
}

Ref<ByteMatrix> Detector::getImage() const
{
    return image_;
}

int  Detector::round(float a)
{
    return (int)(a + 0.5f);
}

//Should be called only once in constructor!
Ref<BitMatrix> Detector::binarize(
    Ref<ByteMatrix> matrix)
{
    int size = matrix->getX() * matrix->getY();
    dataBuffer = new unsigned char[size];
    int length = matrix->getX();
    for (int i = 0; i < matrix->getY(); i++)
    {
        memcpy(&dataBuffer[i * length], matrix->getRow(i), length);
    }
    Ref<GreyscaleLuminanceSource> result(new GreyscaleLuminanceSource(
        dataBuffer, matrix->getX(), matrix->getY(), 0, 0, matrix->getX(),
        matrix->getY()));
    HybridBinarizer hb(result);

    return hb.getBlackMatrix();
}

Ref<ByteMatrix> Detector:: sampleGrid(Ref<ByteMatrix> image, int dimension,
    Ref<PerspectiveTransform> transform)
{
    GreyscaleGridSampler &sampler = GreyscaleGridSampler::getInstance();
    return sampler.sampleGrid(image, dimension, transform);
}

void Detector::correctContrast(Ref<ByteMatrix> image)
{
    //We will apply linear ax + b trandormation to all pixel in image.
    int maxX = image->getX();
    int maxY = image->getY();
    unsigned char absoluteMin = 255;
    unsigned char absoluteMax = 0;
    unsigned char current = 0;

    for (int x = 0; x < maxX; x++)
    {
        for(int y = 0; y < maxY; y++)
        {
            current = (*image)(x, y);
            absoluteMax = current > absoluteMax? current: absoluteMax;
            absoluteMin = current < absoluteMin? current: absoluteMin;
        }
    }
    float a = 255.0f / (absoluteMax - absoluteMin);
    float b = - a * absoluteMin;

    for (int x = 0; x < maxX; x++)
    {
        for(int y = 0; y < maxY; y++)
        {
            current = (*image)(x, y);
            current = (unsigned char)round(a * current + b);
            (*image)(x, y) = (unsigned char)round(
                        255 * pow(current / 255.f, 1.1f));
        }
    }
}

Detector::~Detector()
{
    delete[] dataBuffer;
}

}
}
